package com.cyngn.kafka.produce; import com.cyngn.kafka.config.ConfigConstants; import io.vertx.core.AbstractVerticle; import io.vertx.core.Future; import io.vertx.core.eventbus.Message; import io.vertx.core.json.JsonObject; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Module to listen for messages from vertx event bus and send them to Kafka defaultTopic. * * @author asarda@cyngn.com (Ajay Sarda) on 8/14/15. */ public class MessageProducer extends AbstractVerticle { private KafkaProducer producer; public static String EVENTBUS_DEFAULT_ADDRESS = "kafka.message.publisher"; private String busAddress; private static final Logger logger = LoggerFactory.getLogger(MessageProducer.class); private String defaultTopic; private JsonObject producerConfig; private ExecutorService sender; @Override public void start(final Future<Void> startedResult) { try { producerConfig = config(); Properties properties = populateKafkaConfig(); busAddress = producerConfig.getString(ConfigConstants.EVENTBUS_ADDRESS, EVENTBUS_DEFAULT_ADDRESS); defaultTopic = producerConfig.getString(ConfigConstants.DEFAULT_TOPIC); sender = Executors.newSingleThreadExecutor(); producer = new KafkaProducer(properties); vertx.eventBus().consumer(busAddress, (Message<JsonObject> message) -> sendMessage(message)); Runtime.getRuntime().addShutdownHook(new Thread() { // try to disconnect from ZK as gracefully as possible public void run() { shutdown(); } }); startedResult.complete(); } catch (Exception ex) { logger.error("Message producer initialization failed with ex: {}", ex); startedResult.fail(ex); } } /** * Send a message on a pre-configured defaultTopic. * * @param message the message to send */ public void sendMessage(Message<JsonObject> message) { JsonObject payload = message.body(); ProducerRecord<String,String> record; if (!payload.containsKey(KafkaPublisher.TYPE_FIELD)) { logger.error("Invalid message sent missing {} field, msg: {}", KafkaPublisher.TYPE_FIELD, message); return; } KafkaPublisher.MessageType type = KafkaPublisher.MessageType.fromInt(payload.getInteger(KafkaPublisher.TYPE_FIELD)); String value = payload.getString(ConfigConstants.VALUE_FIELD); switch (type) { case SIMPLE: record = new ProducerRecord(defaultTopic, value); break; case CUSTOM_TOPIC: record = new ProducerRecord(payload.getString(ConfigConstants.TOPIC_FIELD), value); break; case CUSTOM_KEY: record = new ProducerRecord(payload.getString(ConfigConstants.TOPIC_FIELD), payload.getString(ConfigConstants.KEY_FIELD), value); break; case CUSTOM_PARTITION: record = new ProducerRecord(payload.getString(ConfigConstants.TOPIC_FIELD), payload.getInteger(ConfigConstants.PARTITION_FIELD), payload.getString(ConfigConstants.KEY_FIELD), value); break; default: String error = String.format("Invalid type submitted: {} message being thrown away: %s", type.toString(), value); logger.error(error); message.fail(-1, error); return; } sender.submit(() -> producer.send(record, (metadata, exception) -> { if (exception != null) { vertx.runOnContext(aVoid -> message.fail(-1, exception.getMessage())); exception.printStackTrace(); logger.error("Failed to send message to kafka ex: ", exception); // reply with nothing for now } else { vertx.runOnContext(aVoid -> message.reply(new JsonObject())); } }) ); } private Properties populateKafkaConfig() { Properties properties = new Properties(); properties.put(ConfigConstants.BOOTSTRAP_SERVERS, getRequiredConfig(ConfigConstants.BOOTSTRAP_SERVERS)); // default serializer to the String one String defaultSerializer = producerConfig.getString(ConfigConstants.SERIALIZER_CLASS, ConfigConstants.DEFAULT_SERIALIZER_CLASS); properties.put(ConfigConstants.SERIALIZER_CLASS, defaultSerializer); properties.put(ConfigConstants.KEY_SERIALIZER_CLASS, producerConfig.getString(ConfigConstants.KEY_SERIALIZER_CLASS, defaultSerializer)); properties.put(ConfigConstants.VALUE_SERIALIZER_CLASS, producerConfig.getString(ConfigConstants.VALUE_SERIALIZER_CLASS, defaultSerializer)); properties.put(ConfigConstants.PRODUCER_TYPE, producerConfig.getString(ConfigConstants.PRODUCER_TYPE, "async")); properties.put(ConfigConstants.MAX_BLOCK_MS, producerConfig.getLong(ConfigConstants.MAX_BLOCK_MS, new Long(60000))); return properties; } private String getRequiredConfig(String key) { String value = producerConfig.getString(key, null); if (null == value) { throw new IllegalArgumentException(String.format("Required config value not found key: %s", key)); } return value; } private void shutdown() { try { if (producer != null) { producer.close(); producer = null; } if (sender != null) { sender.shutdown(); sender = null; } } catch (Exception ex) { logger.error("Failed to close producer", ex); } } }